You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
204 lines
8.1 KiB
204 lines
8.1 KiB
import Link from "next/link";
|
|
import { BillingPurchaseActions } from "@/components/billing-purchase-actions";
|
|
import {
|
|
getBillingCatalog,
|
|
getBillingPointsPackages,
|
|
getBillingSubscriptionPlans,
|
|
getViewerSession,
|
|
} from "@/lib/popiart-api";
|
|
import { getDictionary, type Locale } from "@/lib/site-content";
|
|
|
|
export default async function PricingPage({
|
|
params,
|
|
}: {
|
|
params: Promise<{ locale: string }>;
|
|
}) {
|
|
const { locale } = await params;
|
|
const typedLocale = locale as Locale;
|
|
const dictionary = getDictionary(typedLocale);
|
|
const session = await getViewerSession();
|
|
const isZh = typedLocale === "zh";
|
|
let plans = dictionary.pricing.plans;
|
|
let subscriptionPlans: Awaited<ReturnType<typeof getBillingSubscriptionPlans>> = null;
|
|
let pointsPackages: Awaited<ReturnType<typeof getBillingPointsPackages>> = null;
|
|
let billingLiveError = "";
|
|
const bindHint = isZh
|
|
? "如需查看真实套餐与发起支付,请先在控制台绑定网关用户态。"
|
|
: "Bind a gateway user in the console first to see live plans and start checkout.";
|
|
const purchaseLabels = {
|
|
alipay: isZh ? "支付宝支付" : "Pay with Alipay",
|
|
wxpay: isZh ? "微信支付" : "Pay with WeChat",
|
|
creating: isZh ? "创建中..." : "Creating...",
|
|
tradeNo: isZh ? "交易号" : "Trade no",
|
|
openLink: isZh ? "打开支付链接" : "Open payment link",
|
|
codeUrl: isZh ? "扫码地址" : "Code URL",
|
|
invalid: isZh ? "创建支付失败。" : "Failed to create payment.",
|
|
pending: isZh ? "支付处理中" : "Payment pending",
|
|
success: isZh ? "支付成功" : "Payment successful",
|
|
failed: isZh ? "支付失败" : "Payment failed",
|
|
paymentStatus: isZh ? "支付状态" : "Payment status",
|
|
qrTitle: isZh ? "微信扫码支付" : "Scan with WeChat",
|
|
openConsole: isZh ? "前往控制台" : "Open console",
|
|
openBilling: isZh ? "查看账单中心" : "Open billing",
|
|
};
|
|
|
|
try {
|
|
const catalog = await getBillingCatalog();
|
|
if (catalog?.plans?.length) {
|
|
plans = catalog.plans.map((plan) => ({
|
|
badge: plan.badge,
|
|
name: plan.name,
|
|
price: plan.price,
|
|
cadence: plan.cadence,
|
|
summary: plan.summary,
|
|
features: plan.features,
|
|
cta: plan.cta,
|
|
highlight: plan.highlight,
|
|
}));
|
|
}
|
|
} catch {
|
|
// Keep the seeded dictionary fallback when the product billing catalog is unavailable.
|
|
}
|
|
|
|
if (session?.gateway_bound) {
|
|
try {
|
|
const [subscriptionResult, pointsResult] = await Promise.all([
|
|
getBillingSubscriptionPlans(),
|
|
getBillingPointsPackages(),
|
|
]);
|
|
subscriptionPlans = subscriptionResult;
|
|
pointsPackages = pointsResult;
|
|
} catch (error) {
|
|
billingLiveError = error instanceof Error ? error.message : String(error);
|
|
}
|
|
}
|
|
|
|
return (
|
|
<div className="page-stack">
|
|
<section className="section-panel hero-tight">
|
|
<div className="section-heading">
|
|
<div className="eyebrow">{dictionary.pricing.tag}</div>
|
|
<h1>{dictionary.pricing.title}</h1>
|
|
<p>{dictionary.pricing.subtitle}</p>
|
|
</div>
|
|
{session ? (
|
|
<div className="status-banner">
|
|
{session.gateway_bound ? (
|
|
<span>{isZh ? "已绑定网关用户,可直接读取真实套餐并发起支付。" : "Gateway user bound. Live plans and checkout are available."}</span>
|
|
) : (
|
|
<span>{bindHint}</span>
|
|
)}
|
|
</div>
|
|
) : null}
|
|
{billingLiveError ? <div className="status-banner status-banner-error">{billingLiveError}</div> : null}
|
|
</section>
|
|
|
|
<section className="pricing-grid">
|
|
{(subscriptionPlans?.items.length ? subscriptionPlans.items.map((item) => ({
|
|
key: String(item.plan.id),
|
|
badge: item.plan.recommended ? (isZh ? "推荐方案" : "Recommended") : (isZh ? "订阅方案" : "Subscription"),
|
|
name: item.plan.title,
|
|
price: `${item.plan.currency} ${item.plan.price_amount}`,
|
|
cadence: `${item.plan.duration_value} ${item.plan.duration_unit}`,
|
|
summary: item.plan.description || item.plan.subtitle,
|
|
features: [
|
|
`${item.plan.points_amount} ${isZh ? "订阅赠送积分" : "subscription points"}`,
|
|
`${item.plan.total_amount || 0} ${isZh ? "总额度" : "total quota"}`,
|
|
`${isZh ? "会员等级" : "member level"}: ${item.plan.member_level || "-"}`,
|
|
`${isZh ? "重置周期" : "reset period"}: ${item.plan.quota_reset_period || "-"}`,
|
|
],
|
|
cta: isZh ? "购买订阅" : "Buy subscription",
|
|
highlight: item.plan.recommended,
|
|
itemId: item.plan.id,
|
|
kind: "subscription" as const,
|
|
})) : plans.map((plan) => ({
|
|
key: plan.name,
|
|
badge: plan.badge,
|
|
name: plan.name,
|
|
price: plan.price,
|
|
cadence: plan.cadence,
|
|
summary: plan.summary,
|
|
features: plan.features,
|
|
cta: plan.cta,
|
|
highlight: plan.highlight,
|
|
itemId: 0,
|
|
kind: "subscription" as const,
|
|
}))).map((plan) => (
|
|
<article
|
|
className={`pricing-card ${plan.highlight ? "pricing-card-highlight" : ""}`}
|
|
key={plan.key}
|
|
>
|
|
<div className="pricing-top">
|
|
<div>
|
|
<span className="card-kicker">{plan.badge}</span>
|
|
<h2>{plan.name}</h2>
|
|
</div>
|
|
<div className="price-line">
|
|
<strong>{plan.price}</strong>
|
|
<span>{plan.cadence}</span>
|
|
</div>
|
|
</div>
|
|
<p className="pricing-summary">{plan.summary}</p>
|
|
<ul className="bullet-list">
|
|
{plan.features.map((feature) => (
|
|
<li key={feature}>{feature}</li>
|
|
))}
|
|
</ul>
|
|
{session?.gateway_bound && plan.itemId > 0 ? (
|
|
<BillingPurchaseActions itemId={plan.itemId} kind={plan.kind} labels={purchaseLabels} />
|
|
) : (
|
|
<Link className="button button-dark" href={`/${locale}/login`}>
|
|
{plan.cta}
|
|
</Link>
|
|
)}
|
|
</article>
|
|
))}
|
|
</section>
|
|
|
|
{pointsPackages?.items.length ? (
|
|
<section className="pricing-grid">
|
|
{pointsPackages.items.map((pack) => (
|
|
<article className="pricing-card" key={pack.id}>
|
|
<div className="pricing-top">
|
|
<div>
|
|
<span className="card-kicker">{isZh ? "积分包" : "Points pack"}</span>
|
|
<h2>{pack.name}</h2>
|
|
</div>
|
|
<div className="price-line">
|
|
<strong>{pack.currency} {pack.price_amount}</strong>
|
|
<span>{isZh ? "/包" : "/pack"}</span>
|
|
</div>
|
|
</div>
|
|
<p className="pricing-summary">
|
|
{isZh
|
|
? `到账 ${pack.points_amount + pack.bonus_points} 积分(含赠送 ${pack.bonus_points})`
|
|
: `${pack.points_amount + pack.bonus_points} total points (${pack.bonus_points} bonus)`}
|
|
</p>
|
|
<ul className="bullet-list">
|
|
<li>{isZh ? `基础积分 ${pack.points_amount}` : `Base points ${pack.points_amount}`}</li>
|
|
<li>{isZh ? `赠送积分 ${pack.bonus_points}` : `Bonus points ${pack.bonus_points}`}</li>
|
|
<li>{isZh ? "购买成功后直接进入积分钱包" : "Delivered into your point wallets after payment"}</li>
|
|
</ul>
|
|
<BillingPurchaseActions itemId={pack.id} kind="points" labels={purchaseLabels} />
|
|
</article>
|
|
))}
|
|
</section>
|
|
) : null}
|
|
|
|
<section className="section-panel">
|
|
<div className="section-heading">
|
|
<div className="eyebrow">{dictionary.pricing.faqTag}</div>
|
|
<h2>{dictionary.pricing.faqTitle}</h2>
|
|
</div>
|
|
<div className="faq-grid">
|
|
{dictionary.pricing.faqs.map((faq) => (
|
|
<article className="faq-card" key={faq.question}>
|
|
<h3>{faq.question}</h3>
|
|
<p>{faq.answer}</p>
|
|
</article>
|
|
))}
|
|
</div>
|
|
</section>
|
|
</div>
|
|
);
|
|
}
|
|
|